package com.huangyi.gateway.handler;

import com.huangyi.commoncore.utils.JsonUtil;
import com.huangyi.commondomain.domain.R;
import com.huangyi.commondomain.domain.ResultCode;
import com.huangyi.commondomain.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.reactive.resource.NoResourceFoundException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * 网关统一异常处理器
 */
@Order(-1)
@Configuration
@Slf4j
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {

    /**
     * 网关统一异常处理器
     *
     * @param exchange WebFlux 的请求响应上下文对象
     * @param ex Throwable：捕获到的异常信息
     * @return 表示异步返回完成
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        // 获取响应对象，用于写回数据
        ServerHttpResponse response = exchange.getResponse();

        // 如果响应已经提交（例如 header/数据已经写出去了），此时无法再修改响应内容
        // 直接返回一个错误的 Mono，让上游知道异常
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        // 默认返回的业务错误码和提示信息
        int retCode = ResultCode.ERROR.getCode();
        String retMsg = ResultCode.ERROR.getMsg();

        // 根据不同异常类型，定制返回的状态码和提示
        if (ex instanceof NoResourceFoundException) {
            // 网关找不到对应的服务
            retCode = ResultCode.SERVICE_NOT_FOUND.getCode();
            retMsg = ResultCode.SERVICE_NOT_FOUND.getMsg();
        } else if (ex instanceof ServiceException) {
            // 自定义的业务异常
            retMsg = ex.getMessage();
            retCode = ((ServiceException) ex).getCode();
        }


        // 约定：业务状态码的前三位就是 HTTP 状态码
        // 例如 404001 → HTTP 404
        int httpCode = Integer.parseInt(String.valueOf(retCode).substring(0, 3));
        log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
        // 调用统一响应写入方法，把错误信息写回客户端
        return webFluxResponseWriter(response, HttpStatus.valueOf(httpCode), retMsg, retCode);
    }


    /**
     * 写回响应（重载方法，默认使用 JSON 格式）
     *
     * @param response 响应对象
     * @param status   HTTP 状态码
     * @param value    响应内容
     * @param code     业务状态码
     * @return Mono<Void>
     */
    private static Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                                    HttpStatus status,
                                                    Object value,
                                                    int code) {
        // 调用完整版，默认 content-type 为 application/json
        return webFluxResponseWriter(response,
                MediaType.APPLICATION_JSON_VALUE,
                status,
                value,
                code);
    }

    /**
     * 写回响应（完整版）
     *
     * @param response    响应对象
     * @param contentType 响应类型（默认 application/json）
     * @param status      HTTP 状态码
     * @param value       响应内容（提示信息或对象）
     * @param code        业务状态码
     * @return Mono<Void>
     */
    private static Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                                    String contentType,
                                                    HttpStatus status,
                                                    Object value,
                                                    int code) {
        // 设置 HTTP 状态码
        response.setStatusCode(status);
        // 设置响应头（告诉前端这是 JSON 数据）
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
//        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, contentType);
        // 构建统一响应体对象：R 是自定义的结果包装类
        // 例如 { "code": 500001, "msg": "服务错误", "data": null }
        R<?> result = R.fail(code, value.toString());
        // 转成 JSON 字符串，并写入 DataBuffer
        DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtil.obj2String(result).getBytes(StandardCharsets.UTF_8));
        // 写回响应体，返回一个异步信号，告诉 WebFlux 写操作完成
        return response.writeWith(Mono.just(dataBuffer));
    }
}